package uk.ac.lkl.common.ui;



import java.awt.Component;
//import java.awt.Container;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;

import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.event.ChangeListener;

import uk.ac.lkl.common.util.ChangeSupport;



/**
 * A class that keeps track of the component it is currently over.
 * 
 * Note that if mouse listeners are added to child components after
 * registration with this class, highlighting may not work properly.
 * 
 * @author Darren Pearce
 * @version $Revision$
 * @version $Date$
 * 
 */
public class HoverTracker<O extends Component> {

    private ChangeSupport changeSupport;

    private O hoverComponent;

    private HashMap<Component, O> childMap;

    private MouseMotionListener mouseMotionListener = new MouseMotionAdapter() {

        public void mouseMoved(MouseEvent e) {
            processMouseMoved(e);
        }
    };

    private MouseListener mouseListener = new MouseAdapter() {

        public void mouseExited(MouseEvent e) {
            HoverTracker.this.hoverComponent = null;
            changeSupport.fireStateChanged();
        }
    };


    public HoverTracker() {
        this.changeSupport = new ChangeSupport(this);
        this.childMap = new HashMap<Component, O>();
    }


    public void addHoverComponent(O component) {
        component.addMouseMotionListener(mouseMotionListener);
        component.addMouseListener(mouseListener);

        ArrayList<Component> components = SwingUtilities2.getMouseComponents(component);

        for (Component childComponent : components) {
            childComponent.addMouseMotionListener(mouseMotionListener);
            childComponent.addMouseListener(mouseListener);
            childMap.put(childComponent, component);
        }
    }


    /**
     * Process a mouse movement.
     * 
     * This method uses the source of the event to determine which
     * component is over. If there is no entry for it in the childMap,
     * the component itself is used. Otherwise its value in the map is
     * used.
     * 
     * @param e
     *            the mouse event
     * 
     */
    private void processMouseMoved(MouseEvent e) {
        Component eventComponent = (O) e.getSource();
        O mappedComponent = childMap.get(eventComponent);

        O hoverComponent = mappedComponent == null ? (O) eventComponent
                : mappedComponent;

        if (hoverComponent == this.hoverComponent)
            return;

        this.hoverComponent = hoverComponent;

        changeSupport.fireStateChanged();
    }


    /**
     * Get the hover component of this instance.
     * 
     * @return
     * 
     */
    public O getHoverComponent() {
        return hoverComponent;
    }


    /**
     * Add a change listener to this instance.
     * 
     * This is notified when the hover component changes or the mouse
     * is over no hover component.
     * 
     * @param listener
     *            the listener to add
     * 
     */
    public void addChangeListener(ChangeListener listener) {
        changeSupport.addListener(listener);
    }


    /**
     * Remove a change listener from this instance.
     * 
     * @param listener
     *            the instance to remove
     * 
     */
    public void removeChangeListener(ChangeListener listener) {
        changeSupport.removeListener(listener);
    }


}
